package net.alche.sdd.servlet.imp;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Renderer;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Rot;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import net.alche.al.util.far.pojo.ExceptionPack;
import net.alche.al.util.far.pojo.PageList;
import net.alche.al.util.far.pojo.ResultPack;
import net.alche.fa.biz.iface.FaHttpRequestResp;
import net.alche.fa.biz.imp.FaReq;
import net.alche.al.util.AC;
import net.alche.al.util.Far;
import net.alche.al.util.M;
import net.alche.al.util.alches.ToolClass;
import net.alche.sdd.comm.SddClassLeader;
import net.alche.sdd.comm.anno.FaBean;
import net.alche.sdd.fatis.SddDao;
import net.alche.sdd.fatis.SddFatis;
import net.alche.sdd.fatis.SddFatisDao;
import net.alche.sdd.fatis.SddFatisStatement;
import net.alche.sdd.pojo.SddCtrlPathNode;
import net.alche.sdd.pojo.SddFilterInfo;
import net.alche.sdd.servlet.SddFilter;
import net.alche.sdd.servlet.biz.SddApplicatonBiz;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("all")
public class SddServletDispatchHot {
  public static final Logger logger = LoggerFactory.getLogger(SddServletDispatchHot.class);
  Map<String, Object> beanMap = new HashMap<>();
  Map<String, Object> beanMapFull = new HashMap<>();
  SddCtrlPathNode rootNode = null;
  SddApplicatonBiz appBiz;
  boolean customerReq = false; // 自定义FaReq
  // Class reqClass = null;
  public String appNamePath = null;
  SddFilter filterBean = null;// 过滤器
  boolean hasLoadFiler = false;
  SddClassLeader leader = null;
  String reqClassName = "";
  int hotCount = 1;

  public SddServletDispatchHot(SddApplicatonBiz appBiz) {
    this.appBiz = appBiz;
    appBiz.beanMap = beanMapFull;
  }

  public void init() {
    if (AC.notEmpty(appBiz.getStr("servlet.reqClass"))) {
      customerReq = true;
      reqClassName = appBiz.getStr("servlet.reqClass");
      // try {
      // // reqClass = Class.forName(appBiz.getStr("servlet.reqClass"));
      // } catch (ClassNotFoundException e) {
      // logger.error("加载 reqClass失败!", appBiz.getStr("servlet.reqClass"));
      // e.printStackTrace();
      // }
    }
    if (appBiz.hasKey("servlet.appName")) {
      appNamePath = "/" + appBiz.getStr("servlet.appName");
    }
  }

  public void writeStaticSource(String path, FaHttpRequestResp req) {
    if (path.contains("WEB-INF")) {
      return;
    }
    try {
      String ext = FileUtil.extName(path);
      String contentType = Far.tl.getContentType(ext);
      if (contentType != null) {
        req.setContentType(contentType);
      }
      byte[] byts = FileUtil.readBytes(AC.system.getClassPath() + "../../" + path);
      if (byts != null) {
        req.writeBytes(byts);
      } else {
        req.setStatues(404);
      }
    } catch (Exception e) {
      req.setStatues(404);
    }
  }

  public void dispatch(String method, String path, FaHttpRequestResp req) {
    if (this.appNamePath != null) {
      path = path.replace(this.appNamePath, "");
    }
    if (path.startsWith("/static/")) {
      writeStaticSource(path, req);
      return;
    }
    if (path.equals("") || path.equals("/")) {
      path = "*index*";
    }
    if (path.equals("/base/serverStart")) {
      return;
    }
    if (path.equals("sdd/hotUpd")) {
      httpReqHotUpd(method, path, req);
      return;
    }
    req.setHttpUrl(path);

    SddCtrlPathNode node = getCtrl(path, method);
    // 开始时间戳
    long beginSec = Far.tl.getCurrentTimeMillis();
    if (node == null || node.method == null) {
      Map mp = AC.newMap(new String[] { "state", "msg" }, new Object[] { 413, M.fmt("请求路径不存在: x1", path) });
      if (!path.equals("/favicon.ico")) {
        logger.info("请求路径不存在: {}", path);
      }
      write(req, mp);
    } else {
      if (!filter(method, path, node, req)) {
        return;
      }
      if (node.nodeName.equals("{*}")) {
        req.setPathAiParam(path.substring(1), node.path);
      }
      try {
        Object obj = null;
        if (this.customerReq) {
          Class reqClass = node.leader.getCls(reqClassName);
          Object deReq = reqClass.getDeclaredConstructor(FaHttpRequestResp.class).newInstance(req);
          obj = node.method.invoke(node.bean, deReq);
        } else {
          obj = node.method.invoke(node.bean, new FaReq(req));
        }
        if (obj != null) {
          if (obj instanceof PageList pageList) {
            ResultPack pack = new ResultPack(pageList);
            if (pageList.getList() != null) {
              pack.setData(pageList.getList());
            }
            pageList.setList(null);
            write(req, pack);
          } else if (obj instanceof ResultPack) {
            write(req, obj);
          } else {
            write(req, new ResultPack(obj));
          }
        }
      } catch (InvocationTargetException ite) {
        if (ite.getTargetException() != null) {
          Throwable th = ite.getTargetException();
          writeInnerExceptin(req, th);
        } else {
          ite.printStackTrace();
          write(req, new ExceptionPack(500, "[请求异常]:" + ite.getMessage()));
        }
      } catch (Exception ex) {
        ex.printStackTrace();
        write(req, new ExceptionPack(500, "[请求异常]:" + ex.getMessage()));
      }
      appBiz.logHttpSec(method, node.path, beginSec);
    }
  }

  public boolean filter(String method, String path, SddCtrlPathNode ctrl, FaHttpRequestResp req) {
    if (!hasLoadFiler) {
      String filterClass = appBiz.getStr("servlet.filterClass");
      if (AC.notEmpty(filterClass)) {
        // Class fClass = null;
        Class fClass = leader.getCls(filterClass);
        // try {
        // fClass = Class.forName(filterClass);
        // } catch (ClassNotFoundException e) {
        // logger.error("加载 reqClass失败!", filterClass);
        // e.printStackTrace();
        // }
        if (fClass != null) {
          try {
            Object filterObj = fClass.getDeclaredConstructor().newInstance();
            filterBean = (SddFilter) filterObj;
            filterBean.setAppBiz(appBiz);
            hasLoadFiler = true;
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    }
    if (filterBean == null)
      return true;
    SddFilterInfo fi = new SddFilterInfo();
    fi.rules = ctrl.rules;
    fi.log = ctrl.log;
    fi.resp = req;
    try {
      Object rb = filterBean.doFilter(path, method, fi);
      if (rb == null) {
        return true;
      } else {
        write(req, rb);
      }
    } catch (Exception e) {
      e.printStackTrace();
      write(req, new ExceptionPack(ExceptionPack.NO_RULE, path));
    }
    return false;
  }

  void writeInnerExceptin(FaHttpRequestResp req, Throwable th) {
    th.printStackTrace();
    Map<String, Object> mp = AC.newMap("state", 0);
    String sourceMsg = th.getMessage();
    String msg = sourceMsg;

    logger.error(req.getHttpType() + "_" + req.getHttpUrl() + ":" + sourceMsg, th);
    String code = null;
    if (msg.startsWith("[") && msg.contains("] ")) {
      code = AC.str.pre(sourceMsg, "] ");
      code = code.substring(1);
      msg = AC.str.suf(sourceMsg, "] ");
    }
    if (code != null) {
      mp.put("code", code);
    }
    mp.put("msg", msg);
    write(req, mp);
  }

  void write(FaHttpRequestResp req, Object val) {
    try {
      if (val != null) {
        req.write(Far.tl.toJsonTxt(val));
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void setBean(String beanName, Object bean) {
    beanMap.put(beanName, bean);
  }

  // 服务启动成功后
  public void serverStartSuccess() {
    SddCtrlPathNode node = getCtrl("base/serverStart", "post");
    if (node != null && node.method != null) {
      try {
        FaHttpRequestResp req = new SddHttpRequestResp(null, null);
        Object obj = null;
        if (this.customerReq) {
          Class reqClass = node.leader.getCls(reqClassName);
          Object deReq = reqClass.getDeclaredConstructor(FaHttpRequestResp.class).newInstance(req);
          obj = node.method.invoke(node.bean, deReq);
        } else {
          obj = node.method.invoke(node.bean, new FaReq(null));
        }
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }

  // 代码热更新
  public void httpReqHotUpd(String method, String path, FaHttpRequestResp req) {
    String names = req.getParam("names");
    String[] sp = names.split(",");
    Map mp = AC.newMap("state", "1");
    loadBeanHot(sp);
    // loadBeanHotFull();
    write(req, mp);
  }

  public void loadBeanHot(String[] names) {
    String[] effect = leader.getHotEffect(names);
    leader = new SddClassLeader(this.leader);
    Map<String, Object> beans = new HashMap<>();
    leader.beans = beans;
    loadBeans(beans, names);
    loadBeans(beans, effect);
    initBeanAutowirte(beans);
    leader.initImports();
    for (Map.Entry<String, Object> item : beans.entrySet()) {
      beanMapFull.put(item.getKey(), item.getValue());
    }
    Far.log("hot-ok, " + "times: [" + (hotCount++) + "]", effect);
  }

  public void loadBeans(Map<String, Object> beans, String[] names) {
    try {
      Class anno = leader.getCls("net.alche.sdd.comm.anno.FaBean");
      for (String name : names) {
        Class cla = leader.loadClass(name);
        if (cla != null && cla.getAnnotation(anno) != null) {
          Object bean = (Object) cla.getDeclaredConstructor().newInstance();
          FaBean ann = bean.getClass().getAnnotation(FaBean.class);
          String beanName = cla.getSimpleName();
          if (AC.notEmpty(ann.name())) {
            beanName = ann.name();
          }
          beans.put(beanName, bean);
          if (ann.type().equals("ctrl")) {
            loadCtrl(ann, bean);
          }
        }
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public void loadBeanHotFull() {
    leader = new SddClassLeader(Far.tl.getClassPath());
    leader.initAll();
    rootNode = null;
    // 加载FaBean
    Map<String, Object> beans = new HashMap<>();
    leader.beans = beans;
    Set<Class<?>> classes = leader.getClassList("net.alche.sdd.comm.anno.FaBean");
    if (classes != null && classes.size() > 0) {
      for (Class<?> cla : classes) {
        try {
          Object bean = (Object) cla.getDeclaredConstructor().newInstance();
          FaBean ann = bean.getClass().getAnnotation(FaBean.class);
          String beanName = cla.getSimpleName();
          if (AC.notEmpty(ann.name())) {
            beanName = ann.name();
          }
          beans.put(beanName, bean);
          if (ann.type().equals("ctrl")) {
            loadCtrl(ann, bean);
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    initBeanAutowirte(beans);
    leader.initImports();
    for (Map.Entry<String, Object> item : beans.entrySet()) {
      beanMapFull.put(item.getKey(), item.getValue());
    }
    for (Map.Entry<String, Object> item : beanMap.entrySet()) {
      beanMapFull.put(item.getKey(), item.getValue());
    }
    Far.log("hot-full-ok!");
  }

  public void beanCheckFatis(Object bean) {
    if (bean instanceof SddFatisDao ss) {
      ss.setFatis(appBiz.fatis());
    }
  }

  public void autowireBean(Map<String, Object> beans, Object bean) {
    beanCheckFatis(bean);
    List<Object> moreLs = new LinkedList<>();
    Field[] fs = bean.getClass().getFields();
    if (fs == null || fs.length <= 0)
      return;
    for (Field fd : fs) {
      FaBean ann = fd.getAnnotation(FaBean.class);
      if (ann != null && ("".equals(ann.type()) || "auto".equals(ann.type()))) {
        Object autoBean = beans.get(fd.getType().getSimpleName());
        if (autoBean == null) {
          autoBean = beanMap.get(fd.getType().getSimpleName());
        }
        if (autoBean == null) {
          Class cls = leader.getCls(fd.getType().getName());
          if (cls != null) {
            try {
              autoBean = cls.getDeclaredConstructor().newInstance();
              moreLs.add(autoBean);
            } catch (Exception e) {
              e.printStackTrace();
            }
          }
        }
        if (autoBean == null) {
          logger.error("{}注入Bean[{}]失败:没有bean的定义！", bean.getClass().getName(), fd.getType().getSimpleName());
        } else {
          try {
            fd.set(bean, autoBean);
          } catch (Exception e) {
            e.printStackTrace();
            logger.error("{}注入Bean[{}]失败:{}", bean.getClass().getName(), fd.getType().getSimpleName(),
                e.getMessage());
          }
        }
      }
    }
    if (!moreLs.isEmpty()) {
      for (Object obj : moreLs) {
        autowireBean(beans, obj);
      }
    }
  }

  public void initBeanAutowirte(Map<String, Object> beans) {
    // 注入bean
    Set<Map.Entry<String, Object>> sets = beans.entrySet();
    List<Object> objLs = new LinkedList<>();
    for (Map.Entry<String, Object> en : sets) {
      objLs.add(en.getValue());
    }
    for (Object obj : objLs) {
      autowireBean(beans, obj);
    }
  }

  // 初始化bean
  public void loadBean(String[] packages) {
    loadBeanHotFull();
  }

  // 初始化bean
  public void loadBean2(String[] packages) {
    if (packages == null) {
      packages = new String[] { "net", "com" };
    }
    leader = new SddClassLeader(Far.tl.getClassPath());
    leader.initAll();
    for (String pack : packages) {
      Set<Class<?>> classes = null;
      try {
        // classes = ToolClass.getClassList(pack, FaBean.class, true);
        // classes = leader.getClassList(FaBean.class);
        classes = leader.getClassList("net.alche.sdd.comm.anno.FaBean");
      } catch (Exception e1) {
        e1.printStackTrace();
      }
      if (classes != null && classes.size() > 0) {
        for (Class<?> cla : classes) {
          try {
            Object bean = (Object) cla.getDeclaredConstructor().newInstance();
            FaBean ann = bean.getClass().getAnnotation(FaBean.class);
            String beanName = cla.getSimpleName();
            if (AC.notEmpty(ann.name())) {
              beanName = ann.name();
            }
            beanMap.put(beanName, bean);
            if (ann.type().equals("ctrl")) {
              loadCtrl(ann, bean);
            }
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    }
    // 注入bean
    Set<Map.Entry<String, Object>> sets = beanMap.entrySet();
    for (Map.Entry<String, Object> en : sets) {
      Object bean = en.getValue();
      if (bean instanceof SddFatisDao ss) {
        ss.setFatis(appBiz.fatis());
      }
      Field[] fs = bean.getClass().getFields();
      if (fs != null && fs.length > 0)
        for (Field fd : fs) {
          FaBean ann = fd.getAnnotation(FaBean.class);
          if (ann != null && ("".equals(ann.type()) || "auto".equals(ann.type()))) {
            Object autoBean = beanMap.get(fd.getType().getSimpleName());
            if (autoBean == null) {
              logger.error("{}注入Bean[{}]失败:没有bean的定义！", bean.getClass().getName(), fd.getType().getSimpleName());
            } else {
              try {
                fd.set(bean, autoBean);
              } catch (Exception e) {
                e.printStackTrace();
                logger.error("{}注入Bean[{}]失败:{}", bean.getClass().getName(), fd.getType().getSimpleName(),
                    e.getMessage());
              }
            }

          }
        }
      // FaBean ann = bean.getClass().getAnnotation(FaBean.class);
      // if(ann)
    }
  }

  void loadCtrl(FaBean parAnn, Object bean) {
    Method[] methods = bean.getClass().getMethods();
    if (methods != null) {
      for (Method md : methods) {
        FaBean ann = md.getAnnotation(FaBean.class);
        if (ann != null) {
          String httpType = ann.type();
          if (httpType != null) {
            initCtrlPath(parAnn, ann, bean, md);
          }
        }
      }
    }
  }

  public SddCtrlPathNode getCtrl(String path, String httpType) {
    SddCtrlPathNode cNode = rootNode;
    if (cNode == null) {
      return null;
    }
    if (path.startsWith("/"))
      path = path.substring(1);
    String[] pps = path.split("/");
    int ppsIndex = 0;
    return getCtrlDeep(rootNode.childs, httpType, pps, 0);
  }

  public SddCtrlPathNode getCtrlDeep(List<SddCtrlPathNode> nodes, String httpType, String[] pps, int ppsIndex) {
    if (!AC.empty(nodes)) {
      for (SddCtrlPathNode nn : nodes) {
        if (nn.httpType.equals(httpType) || nn.httpType.equals("")) {
          if (nn.nodeName.equals(pps[ppsIndex])) {
            if (ppsIndex + 1 == pps.length) {
              return nn;
            }
            SddCtrlPathNode rb = this.getCtrlDeep(nn.childs, httpType, pps, ppsIndex + 1);
            if (rb != null)
              return rb;
          } else if (nn.nodeName.equals("{*}") || nn.nodeName.equals("**")) {
            return nn;
          }
        }
      }
    }
    return null;
  }

  private void initCtrlPath(FaBean beanAnn, FaBean ann, Object bean, Method method) {
    String parPath = beanAnn.value();
    if (parPath.endsWith("/*")) {
      parPath = parPath.substring(0, parPath.length() - 2);
    }
    String url = ann.value();
    String type = ann.type();
    if (type.equals("delete")) {
      type = "del";
    }
    if (!parPath.endsWith("/") && !url.startsWith("/")) {
      url = "/" + url;
    }
    String path = parPath + url;
    if (url.equals("/*index*")) {
      path = "*index*";
    }
    if (path.startsWith("/")) {
      path = path.substring(1);
    }
    String[] pnode = path.split("/");
    if (rootNode == null) {
      rootNode = new SddCtrlPathNode();
      rootNode.nodeName = "*";
    }
    fillNode(pnode, rootNode, 0, type, bean, method, path);
  }

  private void fillNode(String[] pps, SddCtrlPathNode node, int index, String httpType, Object faClass,
      Method faMethod, String path) {
    if (pps.length == index) {
      node.bean = faClass;
      node.method = faMethod;
      node.path = path;
      node.leader = leader;
      FaBean ann = faMethod.getAnnotation(FaBean.class);// .getClass().getAnnotation(FaBean.class);
      node.log = ann.log();
      String rule = ann.rule();
      if (rule != null && !rule.equals("")) {
        node.rules = rule.split(",");
      } else {
        node.rules = null;
      }
      return;
    }
    String nName = pps[index];
    if (nName.startsWith("{")) {
      nName = "{*}";
    }
    SddCtrlPathNode cNode = null;
    if (node.childs != null) {
      for (SddCtrlPathNode n : node.childs) {
        if (n.httpType.equals(httpType) && n.nodeName.equals(nName)) {
          cNode = n;
          break;
        }
      }
    }
    if (cNode != null && pps.length <= index + 1 && !cNode.httpType.equals(httpType)) {
      cNode = null;
    }
    if (cNode == null) {
      if (node.childs == null) {
        node.childs = new ArrayList<>();
      }
      cNode = new SddCtrlPathNode();
      cNode.nodeName = nName;
      cNode.httpType = httpType;
      node.childs.add(cNode);
    }
    fillNode(pps, cNode, index + 1, httpType, faClass, faMethod, path);
  }
}
